/*
 * Copyright 2010 Katsunori Koyanagi
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package sweezy.anim.easing
{
	import sweezy.anim.core.IEasing;

	public class Easing
	{

		private static var _easings:Object = {};

		private static var _initialized:Boolean = false;

		public static function concat(... easings):IEasing
		{
			if (easings.length === 1 && easings is Array)
			{
				return new ConcatenatedEasing(easings[0]);
			}

			return new ConcatenatedEasing(easings);
		}

		public static function convert(easing:*):IEasing
		{
			if (easing === null)
			{
				return null;
			}

			return new AdapterEasing(easing);
		}

		public static function ease(name:String, fallbackEaseNone:Boolean = false):IEasing
		{
			return ease0(name, "", fallbackEaseNone);
		}

		public static function easeIn(name:String, fallbackEaseNone:Boolean = false):IEasing
		{
			return ease0(name, "easein", fallbackEaseNone);
		}

		public static function easeInOut(name:String, fallbackEaseNone:Boolean = false):IEasing
		{
			return ease0(name, "easeinout", fallbackEaseNone);
		}

		public static function easeOut(name:String, fallbackEaseNone:Boolean = false):IEasing
		{
			return ease0(name, "easeout", fallbackEaseNone);
		}

		public static function easeOutIn(name:String, fallbackEaseNone:Boolean = false):IEasing
		{
			return ease0(name, "easeoutin", fallbackEaseNone);
		}

		public static function merge(... easings):IEasing
		{
			if (easings.length === 1 && easings is Array)
			{
				return new MergedEasing(easings[0]);
			}
			
			return new MergedEasing(easings);
		}

		private static function ease0(name:String, suffix:String, fallbackEaseNone:Boolean):IEasing
		{
			if (!name)
			{
				return fallbackEaseNone ? Linear.easeNone : null;
			}

			if (!_initialized)
			{
				_initialized = true;
				init();
			}

			name = name.toLocaleLowerCase() + suffix;
			if (name in _easings)
			{
				return _easings[name];
			}

			return fallbackEaseNone ? Linear.easeNone : null;
		}

		private static function init():void
		{
			var e:Object = {};
			e["back"] = Back;
			e["bounce"] = Bounce;
			e["circ"] = Circular;
			e["circular"] = Circular;
			e["cubic"] = Cubic;
			e["elastic"] = Elastic;
			e["expo"] = Exponential;
			e["exponential"] = Exponential;
			e["linear"] = Linear;
			e["quad"] = Quadratic;
			e["quadratic"] = Quadratic;
			e["quart"] = Quartic;
			e["quartic"] = Quartic;
			e["quint"] = Quintic;
			e["quintic"] = Quintic;
			e["strong"] = Quintic;
			e["sine"] = Sine;

			for (var name:String in e)
			{
				var type:Class = e[name];
				_easings[name + "easein"] = type["easeIn"];
				_easings[name + "easeinout"] = type["easeInOut"];
				_easings[name + "easeout"] = type["easeOut"];
				_easings[name + "easeoutin"] = type["easeOutIn"];
			}

			_easings["noneeasein"] = Linear.easeNone;
			_easings["noneeaseinout"] = Linear.easeNone;
			_easings["noneeaseout"] = Linear.easeNone;
			_easings["noneeaseoutin"] = Linear.easeNone;
		}
	}
}

import sweezy.anim.core.IEasing;

class AdapterEasing implements IEasing
{

	private var _args:Array;

	private var _easing:*;

	private var _len:uint;

	private var _type:uint;

	public function AdapterEasing(easing:*)
	{
		if (easing is Function)
		{
			var len:uint = easing.length;
			switch (len)
			{
				case 0:
					break;
				case 1:
					_type = 1;
					break;
				case 2:
					_type = 2;
					break;
				case 3:
					break;
				case 4:
					_type = 3;
					break;
				default:
					_args = [];
					_args[1] = 0;
					_args[2] = 1;
					_args[3] = 1;
					for (var i:int = 4; i < len; i++)
					{
						_args[i] = null;
					}

					_type = 4;
					break;
			}
		}
		else if (easing is IEasing)
		{
			_type = 6;
		}

		if (_type > 0)
		{
			_easing = easing;
		}
	}

	public function ease(fraction:Number):Number
	{
		switch (_type)
		{
			case 1:
				return _easing(fraction);
			case 2:
				return _easing(fraction, 1);
			case 3:
				return _easing(fraction, 0, 1, 1);
			case 4:
				_args[0] = fraction;
				return _easing.apply(null, _args);
			case 5:
				return _easing.ease(fraction);
		}

		return fraction;
	}
}

class MergedEasing implements IEasing
{

	private var _easings:Array;

	private var _len:uint;

	public function MergedEasing(easings:Array)
	{
		_easings = [];

		for each (var easing:Object in easings)
		{
			if (easing is IEasing)
			{
				_easings[_easings.length] = easing as IEasing;
			}
			else
			{
				_easings[_easings.length] = new AdapterEasing(easings);
			}
		}

		_len = _easings.length;
	}

	public function ease(fraction:Number):Number
	{
		var len:uint = _len;
		if (len === 0)
		{
			return fraction;
		}

		for (var i:int = 0; i < len; i++)
		{
			fraction = _easings[i].ease(fraction);
		}

		return fraction;
	}
}

class ConcatenatedEasing implements IEasing
{

	private var _easings:Array;

	private var _len:uint;

	public function ConcatenatedEasing(easings:Array)
	{
		_easings = [];

		for each (var easing:Object in easings)
		{
			if (easing is IEasing)
			{
				_easings[_easings.length] = easing as IEasing;
			}
			else
			{
				_easings[_easings.length] = new AdapterEasing(easings);
			}
		}

		_len = _easings.length;
	}

	public function ease(fraction:Number):Number
	{
		var len:uint = _len;
		if (len === 0)
		{
			return fraction;
		}

		var index:uint = fraction * len;
		if (index >= len)
		{
			index = len - 1;
		}
		else if (index < 0)
		{
			index = 0;
		}

		var easing:IEasing = _easings[index];
		var result:Number = easing.ease(fraction * len - index) / len;
		result += index / len;

		return result;
	}
}
